import Axios, {
  type AxiosInstance,
  type AxiosRequestConfig,
  type CustomParamsSerializer
} from "axios";
import type {
  PureHttpError,
  RequestMethods,
  PureHttpResponse,
  PureHttpRequestConfig
} from "./types.d";
import { stringify } from "qs";
import NProgress from "../progress";
import { getToken, formatToken, removeToken } from "@/utils/auth";

import { message } from "@/utils/message";
import { ElMessage, ElMessageBox } from "element-plus";
import { router } from "@/router";
import { downloadByData } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user";

// 相关配置请参考：www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 20000,
  headers: {
    Accept: "application/json, text/plain, */*",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest"
  },
  // 数组格式参数序列化（https://github.com/axios/axios/issues/5142）
  paramsSerializer: {
    serialize: stringify as unknown as CustomParamsSerializer
  }
};

class PureHttp {
  constructor() {
    this.httpInterceptorsRequest();
    this.httpInterceptorsResponse();
  }

  /** `token`过期后，暂存待执行的请求 */
  private static requests = [];

  /** 防止重复刷新`token` */
  private static isRefreshing = false;

  /** 防止重复窗口 */
  private static isShowLoginDialog = false;

  /** 初始化配置对象 */
  private static initConfig: PureHttpRequestConfig = {};

  /** 保存当前`Axios`实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);

  /** 重连原始请求 */
  private static retryOriginalRequest(config: PureHttpRequestConfig) {
    return new Promise(resolve => {
      PureHttp.requests.push((token: string) => {
        config.headers["Authorization"] = formatToken(token);
        resolve(config);
      });
    });
  }

  /** 请求拦截 */
  private httpInterceptorsRequest(): void {
    PureHttp.axiosInstance.interceptors.request.use(
      async (config: PureHttpRequestConfig): Promise<any> => {
        // 开启进度条动画
        NProgress.start();
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof config.beforeRequestCallback === "function") {
          config.beforeRequestCallback(config);
          return config;
        }
        if (PureHttp.initConfig.beforeRequestCallback) {
          PureHttp.initConfig.beforeRequestCallback(config);
          return config;
        }
        /** 请求白名单，放置一些不需要`token`的接口（通过设置请求白名单，防止`token`过期后再请求造成的死循环问题） */
        const whiteList = ["/auth/login"];
        return whiteList.some(v => config.url.endsWith(v))
          ? config
          : new Promise(resolve => {
              const data = getToken();
              const now = new Date().getTime();
              if (data.expires - now <= 0) {
                // 刷新token
                config.headers["Authorization"] = formatToken(
                  data.refreshToken
                );
                resolve(config);
              } else {
                config.headers["Authorization"] = formatToken(data.accessToken);
                resolve(config);
              }
            });
        // return whiteList.some(url => config.url.endsWith(url))
        //   ? config
        //   : new Promise(resolve => {
        //       const data = getToken();
        //       if (data) {
        //         const now = new Date().getTime();
        //         const expired = data.expires - now <= 0;
        //         if (expired) {
        //           if (!PureHttp.isRefreshing) {
        //             PureHttp.isRefreshing = true;
        //             // token过期刷新
        //             useUserStoreHook()
        //               .handRefreshToken({ refreshToken: data.refreshToken })
        //               .then(res => {
        //                 const token = res.accessToken;
        //                 config.headers["Authorization"] = formatToken(token);
        //                 PureHttp.requests.forEach(cb => cb(token));
        //                 PureHttp.requests = [];
        //               })
        //               .catch(() => {
        //                 PureHttp.requests = [];
        //                 removeToken();
        //                 router.push("/login");
        //                 message("登录已过期，请重新登录", { type: "warning" });
        //               })
        //               .finally(() => {
        //                 PureHttp.isRefreshing = false;
        //               });
        //           }
        //           resolve(PureHttp.retryOriginalRequest(config));
        //         } else {
        //           config.headers["Authorization"] = formatToken(
        //             data.accessToken
        //           );
        //           resolve(config);
        //         }
        //       } else {
        //         resolve(config);
        //       }
        //     });
      },
      error => {
        return Promise.reject(error);
      }
    );
  }

  /** 响应拦截 */
  private httpInterceptorsResponse(): void {
    const instance = PureHttp.axiosInstance;
    instance.interceptors.response.use(
      async (response: PureHttpResponse) => {
        let code = undefined;
        let msg = undefined;
        // 后台返回的二进制流
        if (response.data instanceof Blob) {
          // 返回二进制流的时候 可能出错  这时候返回的错误是Json格式
          if (response.data.type === "application/json") {
            const text = await this.readBlobAsText(response.data);
            const json = JSON.parse(text);
            // 提取错误消息中的code和msg
            code = json.code;
            msg = json.msg;
          } else {
            NProgress.done();
            return response.data;
          }
          // 正常的返回类型 直接获取code和msg字段
        } else {
          code = response.data.code;
          msg = response.data.msg;
        }
        // 如果不存在code说明后端格式有问题
        if (!code) {
          message("服务器返回数据结构有误", { type: "error" });
          NProgress.done();
          return Promise.reject(new Error("无效响应结构"));
        }
        // 请求返回失败时，有业务错误时，弹出错误提示
        if (response.data.code !== 200) {
          // token失效时弹出过期提示
          if (response.data.code === 401) {
            if (!PureHttp.isShowLoginDialog) {
              PureHttp.isShowLoginDialog = true;
              ElMessageBox.confirm(
                "登录状态已过期，您可以继续留在该页面，或者重新登录",
                "系统提示",
                {
                  confirmButtonText: "重新登录",
                  cancelButtonText: "取消",
                  type: "warning"
                }
              )
                .then(() => {
                  PureHttp.isShowLoginDialog = false;
                  removeToken();
                  router.push("/login");
                })
                .catch(() => {
                  PureHttp.isShowLoginDialog = false;
                  message("取消重新登录", { type: "info" });
                });
            }
          } else if (response.data.code === 403) {
            message(msg || "请求失败", { type: "error" });
            router.push("/error/403");
          } else {
            // 其余情况弹出错误提示框
            message(msg || "请求失败", { type: "error" });
          }
          NProgress.done();
          return Promise.reject(msg);
        }
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调

        const $config = response.config;
        // 关闭进度条动画
        NProgress.done();
        if (typeof $config.beforeResponseCallback === "function") {
          $config.beforeResponseCallback(response);
        } else if (PureHttp.initConfig.beforeResponseCallback) {
          PureHttp.initConfig.beforeResponseCallback(response);
        }
        return response.data.data;
      },
      (error: PureHttpError) => {
        const $error = error;
        $error.isCancelRequest = Axios.isCancel($error);
        // 关闭进度条动画
        NProgress.done();
        // 全局 HTTP 错误（如请求失败、超时等）
        if (typeof error === "string") {
          ElMessage.error(error);
        } else if (
          error.code === "ECONNABORTED" &&
          error.message.includes("timeout")
        ) {
          ElMessage.error("请求超时，请检查网络或稍后重试");
        } else {
          ElMessage.error("请求发生错误，请稍后重试");
        }
        // 所有的响应异常 区分来源为取消请求/非取消请求
        return Promise.reject($error);
      }
    );
  }
  /** 从二进制流中读取文本 */
  async readBlobAsText(blob: Blob): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        const text = reader.result as string;
        resolve(text);
      };
      reader.onerror = reject;
      reader.readAsText(blob, "UTF-8");
    });
  }
  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: PureHttpRequestConfig
  ): Promise<T> {
    const config = {
      method,
      url,
      ...param,
      ...axiosConfig
    } as PureHttpRequestConfig;

    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      PureHttp.axiosInstance
        .request(config)
        .then((response: undefined) => {
          resolve(response);
        })
        .catch(error => {
          reject(error);
        });
    });
  }
  /** download文件方法 从后端获取文件流 */
  public download(
    url: string,
    fileName: string,
    type: string = "下载",
    params?: AxiosRequestConfig
  ): void {
    this.post(url, params, { responseType: "blob" })
      .then(async (res: Blob) => {
        // 判断是否是 JSON（即失败响应）
        if (res.size === 0) ElMessage.error(`${type}文件失败`);
        else downloadByData(res, fileName);
      })
      .catch(() => {
        ElMessage.error(`请求失败，无法${type}文件`);
      });
  }

  /** 单独抽离的`post`工具函数 */
  public post<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("post", url, params, config);
  }

  /** 单独抽离的`get`工具函数 */
  public get<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("get", url, params, config);
  }

  /** 单独抽离的`upload`工具函数 */
  public upload<T>(
    url: string,
    file: File,
    fieldName = "file",
    extraData: Record<string, any> = {},
    config?: PureHttpRequestConfig
  ): Promise<T> {
    const formData = new FormData();
    formData.append(fieldName, file);

    // 添加附加表单字段
    for (const [key, value] of Object.entries(extraData)) {
      formData.append(key, value);
    }

    const uploadConfig: PureHttpRequestConfig = {
      headers: {
        "Content-Type": "multipart/form-data"
      },
      ...config,
      data: formData
    };

    return this.request<T>("post", url, uploadConfig);
  }
}

export const http = new PureHttp();
